package com.webshooter.gamelogic;

import com.webshooter.map.TileMap;
import com.webshooter.math.Vector;
import com.webshooter.entity.Entity;
import com.webshooter.math.IntPair;
import com.webshooter.math.MathUtil;
import java.util.ArrayList;
import java.util.List;

public class ShotCalc {

    private static final float shotRange = 100.0f;
    private final TileMap tileMap;

    public ShotCalc(TileMap tileMap) {
        this.tileMap = tileMap;
    }

    public List<Entity> calcEntitiesHitByShot(Vector shot, Entity entity, List<Entity> allEntities) {
        List<Entity> entities = new ArrayList<>();

        for (Entity currentEntity : allEntities) {
            if (!entity.name.equals(currentEntity.name)
                    && MathUtil.pointToLineSegmentDistance(entity.pos, entity.pos.add(shot), currentEntity.pos) < 20.0f) {
                entities.add(currentEntity);
            }
        }

        return entities;
    }

    public Vector calcShot(Entity entity, float shootAngle) {
        Vector endPointByX = calcEndPointByX(entity, shootAngle);
        Vector endPointByY = calcEndPointByY(entity, shootAngle);
        Vector endVectorByX = endPointByX.subtract(entity.pos);
        Vector endVectorByY = endPointByY.subtract(entity.pos);
        if (endVectorByX.norm() < endVectorByY.norm()) {
            return endVectorByX;
        } else {
            return endVectorByY;
        }
    }

    public Vector calcEndPointByX(Entity entity, float shootAngle) {
        Vector maxEndOfShot = entity.pos.add(calcMaxShot(shootAngle));

        IntPair startCoords = tileMap.getTileCoords(entity.pos);
        IntPair endCoords = tileMap.getTileCoords(maxEndOfShot);

        int delta = Integer.signum(endCoords.x - startCoords.x);
        for (int i = startCoords.x; i != endCoords.x; i += delta) {
            Vector border = intersectLineWithHorizontalGridBorder(entity.pos, maxEndOfShot, i, delta);
            Vector adjacentCenter = border.add(new Vector(delta * TileMap.tileWidth / 2, 0));
            if (!tileMap.isPassable(tileMap.getTileCoords(adjacentCenter))) {
                return border;
            }
        }
        return maxEndOfShot;
    }

    public Vector calcEndPointByY(Entity entity, float shootAngle) {
        Vector maxEndOfShot = entity.pos.add(calcMaxShot(shootAngle));

        IntPair startCoords = tileMap.getTileCoords(entity.pos);
        IntPair endCoords = tileMap.getTileCoords(maxEndOfShot);

        int delta = Integer.signum(endCoords.y - startCoords.y);
        for (int i = startCoords.y; i != endCoords.y; i += delta) {
            Vector border = intersectLineWithVerticalGridBorder(entity.pos, maxEndOfShot, i, delta);
            Vector adjacentCenter = border.add(new Vector(0, delta * TileMap.tileHeight / 2));
            if (!tileMap.isPassable(tileMap.getTileCoords(adjacentCenter))) {
                return border;
            }
        }
        return maxEndOfShot;
    }

    private static Vector calcMaxShot(float shootAngle) {
        return new Vector(Math.round(shotRange * Math.cos(shootAngle) * TileMap.horizontalNumTiles),
                Math.round(shotRange * Math.sin(shootAngle) * TileMap.verticalNumTiles));
    }

    private static Vector intersectLineWithHorizontalGridBorder(Vector start, Vector end, int gridX, int delta) {
        int rightGridX = Math.max(gridX, gridX + delta);
        int borderX = rightGridX * TileMap.tileWidth;
        if (delta < 0) {
            borderX--;
        }
        float diffX = borderX - start.x;
        float totalX = end.x - start.x;
        float totalY = end.y - start.y;
        float diffY = (diffX / totalX) * totalY;
        return start.add(new Vector(diffX, diffY));
    }

    private static Vector intersectLineWithVerticalGridBorder(Vector start, Vector end, int gridY, int delta) {
        int bottomGridY = Math.max(gridY, gridY + delta);
        int borderY = bottomGridY * TileMap.tileHeight;
        if (delta < 0) {
            borderY--;
        }
        float diffY = borderY - start.y;
        float totalX = end.x - start.x;
        float totalY = end.y - start.y;
        float diffX = (diffY / totalY) * totalX;
        return start.add(new Vector(diffX, diffY));
    }
}
